package de.hochschuletrier.bachelor.antfarm.entity.ant;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;

import de.hochschuletrier.bachelor.antfarm.entity.Ant;
import de.hochschuletrier.bachelor.antfarm.entity.Foodsource;
import de.hochschuletrier.bachelor.antfarm.entity.mes.ActionMessage;
import de.hochschuletrier.bachelor.antfarm.entity.mes.ActionMessageGroup;
import de.hochschuletrier.bachelor.antfarm.util.DistanceHelper;
import de.hochschuletrier.bachelor.antfarm.world.Direction;
import de.hochschuletrier.bachelor.antfarm.world.Map;
import de.hochschuletrier.bachelor.antfarm.world.World;

public abstract class Antstate {
	public abstract void perform(final Ant ant);

	// Helper-Funktionen

	/** Mögliche Richtungen ausloten */
	protected ArrayList<Point> getDirections(final Ant ant, final ActionMessageGroup g) {

		ArrayList<Point> possibleDirections = new ArrayList<Point>();
		for (int x = -1; x <= 1; ++x) {
			for (int y = -1; y <= 1; ++y) {
				if (y != 0 || x != 0) {
					possibleDirections.add(new Point(x, y));
				}
			}
		}
		g.addMessage(new ActionMessage("Neue Auswahl an Punkten (aktuell: " + ant.getX() + "|" + ant.getY() + ")"));
		return possibleDirections;
	}

	/** Aus dem Level herauslaufen verhindern */
	protected ArrayList<Point> preventGettingOutOfLevel(final ArrayList<Point> possibleDirections, final Ant ant, final ActionMessageGroup g) {
		for (int i = possibleDirections.size() - 1; i >= 0; --i) {
			Point currentPoint = possibleDirections.get(i);
			boolean remove = false;

			Map map = World.getInstance().getMap();
			if (ant.getX() + currentPoint.x < 0 || ant.getX() + currentPoint.x >= map.getWidth() || ant.getY() + currentPoint.y < 0
					|| ant.getY() + currentPoint.y >= map.getHeight()) {
				remove = true;
			}

			// Auch durch Nest laufen verhindern
			if (!remove && ant.getX() + currentPoint.x == ant.getNest().getX() && ant.getY() + currentPoint.y == ant.getNest().getY()) {
				remove = true;
			}

			// Nicht durch Hindernisse laufen
			if (!remove && World.getInstance().getMap().isTileBlocked(ant.getX() + currentPoint.x, ant.getY() + currentPoint.y)) {
				remove = true;
			}

			if (remove) {
				g.addMessage(new ActionMessage("Habe Punkt (" + (ant.getX() + possibleDirections.get(i).x) + "|" + (ant.getY() + possibleDirections.get(i).y)
						+ ") entfernt um nicht aus dem Level zu laufen", possibleDirections.get(i)));
				possibleDirections.remove(i);
			}
		}
		return possibleDirections;
	}

	protected boolean getToFoodsource(final ArrayList<Point> possibleDirections, final Ant ant, final ActionMessageGroup g) {
		if (ant.isFoodsourceNearby()) {
			Foodsource foodsource = ant.getNextFoodsource();

			// Ist die Ameise schon an der Quelle?
			if (DistanceHelper.getDistance(ant.getX(), ant.getY(), foodsource.getX(), foodsource.getY()) <= 1) {
				ant.setFoodpiece(foodsource.takeFoodpiece());
				ant.getMessages().clear();
				ant.setState(new Returnstate(ant));
				ant.addMessage(g);
				g.addMessage(new ActionMessage("Habe Futter genommen!"));
				return true;
			}
			// Näher an die Nahrungsquelle kommen
			else {
				float dist = -1;
				for (Point p : possibleDirections) {
					float currentDistance = DistanceHelper.getDistance(foodsource.getX(), foodsource.getY(), ant.getX() + p.x, ant.getY() + p.y);
					if (dist == -1 || currentDistance < dist) {
						dist = currentDistance;
					}
				}

				for (int i = possibleDirections.size() - 1; i >= 0; --i) {
					Point p = possibleDirections.get(i);
					float currentDistance = DistanceHelper.getDistance(foodsource.getX(), foodsource.getY(), ant.getX() + p.x, ant.getY() + p.y);
					if (currentDistance > dist) {
						g.addMessage(new ActionMessage("Habe Punkt (" + (ant.getX() + possibleDirections.get(i).x) + "|"
								+ (ant.getY() + possibleDirections.get(i).y) + ") entfernt weil ich damit nicht näher an Futterquelle komme",
								possibleDirections.get(i)));
						possibleDirections.remove(i);
					}
				}
			}
		}
		return false;
	}

	/** Letzte Richtung entfernen */
	protected ArrayList<Point> removeLastDirections(final ArrayList<Point> l, final Ant ant, final ActionMessageGroup g) {
		ArrayList<Point> possibleDirections = new ArrayList<Point>(l);
		if (ant.getLastPositions().size() > 0) {
			for (int index = 0; index < 5; ++index) {
				if (index >= ant.getLastPositions().size()) {
					break;
				}

				Point lastPosition = ant.getLastPositions().get(ant.getLastPositions().size() - 1 - index);
				for (int i = possibleDirections.size() - 1; i >= 0; --i) {
					Point currentPoint = possibleDirections.get(i);
					boolean remove = false;

					if (ant.getX() + currentPoint.x == lastPosition.x && ant.getY() + currentPoint.y == lastPosition.y) {
						remove = true;
					}

					if (remove && possibleDirections.size() > 1) {
						g.addMessage(new ActionMessage("Habe Punkt (" + (ant.getX() + possibleDirections.get(i).x) + "|"
								+ (ant.getY() + possibleDirections.get(i).y) + ") entfernt weil ich dort schon war", possibleDirections.get(i)));
						possibleDirections.remove(i);
					}
				}
			}
		}
		return possibleDirections;
	}

	/** Letzte Richtung schlechter gewichten */
	protected HashMap<Point, Integer> evaluateLastDirections(final ArrayList<Point> l, final Ant ant, HashMap<Point, Integer> weightMap, final ActionMessageGroup g) {
		ArrayList<Point> possibleDirections = new ArrayList<Point>(l);
		if (ant.getLastPositions().size() > 0) {
			for (int index = 0; index < ant.getLastPositions().size(); ++index) {
				if (index >= ant.getLastPositions().size()) {
					break;
				}

				Point lastPosition = ant.getLastPositions().get(ant.getLastPositions().size() - 1 - index);
				for (int i = possibleDirections.size() - 1; i >= 0; --i) {
					Point currentPoint = possibleDirections.get(i);
					boolean used = false;

					if (ant.getX() + currentPoint.x == lastPosition.x && ant.getY() + currentPoint.y == lastPosition.y) {
						used = true;
					}

					if (used) {
						int value = weightMap.get(currentPoint);
						weightMap.remove(currentPoint);
						weightMap.put(currentPoint, value / 5);
					}
				}
			}
		}
		return weightMap;
	}

	/** Bisherige Richtung zum Nest beibehalten */
	protected ArrayList<Point> holdDirectionToNest(final ArrayList<Point> l, final Ant ant, final ActionMessageGroup g, boolean fromNestAway) {
		ArrayList<Point> possibleDirections = new ArrayList<Point>(l);
		int directionSimilarities = 0; // enthält, ob eine Richtung der
										// bisherigen in x- und/oder y-Richtung
										// entspricht

		for (int i = possibleDirections.size() - 1; i >= 0; --i) {
			Point currentPoint = possibleDirections.get(i);

			int distXToNest = ant.getNest().getX() - ant.getX();
			int distYToNest = ant.getNest().getY() - ant.getY();
			if (distXToNest != 0 || distYToNest != 0) {
				Point lastDirection = Direction.getDirection(Direction.getDirection(distXToNest, distYToNest));
				int currentSimiliarities = 0;
				if ((currentPoint.x == -lastDirection.x && fromNestAway) || (currentPoint.x == lastDirection.x && !fromNestAway)) {
					currentSimiliarities++;
				}
				if ((currentPoint.y == -lastDirection.y && fromNestAway) || (currentPoint.y == lastDirection.y && !fromNestAway)) {
					currentSimiliarities++;
				}
				if (currentSimiliarities > directionSimilarities) {
					directionSimilarities = currentSimiliarities;
				}
			}
		}

		int distXToNest = ant.getNest().getX() - ant.getX();
		int distYToNest = ant.getNest().getY() - ant.getY();
		if (distXToNest != 0 || distYToNest != 0) {
			Point lastDirection = Direction.getDirection(Direction.getDirection(distXToNest, distYToNest));

			for (int i = possibleDirections.size() - 1; i >= 0; --i) {
				Point currentPoint = possibleDirections.get(i);

				int currentSimiliarities = 0;
				if ((currentPoint.x == -lastDirection.x && fromNestAway) || (currentPoint.x == lastDirection.x && !fromNestAway)) {
					currentSimiliarities++;
				}
				if ((currentPoint.y == -lastDirection.y && fromNestAway) || (currentPoint.y == lastDirection.y && !fromNestAway)) {
					currentSimiliarities++;
				}
				if (currentSimiliarities < directionSimilarities) {
					g.addMessage(new ActionMessage("Habe Punkt (" + (ant.getX() + possibleDirections.get(i).x) + "|"
							+ (ant.getY() + possibleDirections.get(i).y) + ") entfernt weil dieser nicht meiner Richtung von Nest aus entsprach",
							possibleDirections.get(i)));
					possibleDirections.remove(i);
				}
			}
		}
		return possibleDirections;
	}
	
	/** Bisherige Richtung zum Nest beibehalten -> Gewichtet */
	protected HashMap<Point, Integer> holdDirectionToNest(final ArrayList<Point> l, final Ant ant, HashMap<Point, Integer> weightMap, final ActionMessageGroup g, boolean fromNestAway) {
		
		ArrayList<Point> possibleDirections = new ArrayList<Point>(l);
		int directionSimilarities = 0; // enthält, ob eine Richtung der
										// bisherigen in x- und/oder y-Richtung
										// entspricht

		for (int i = possibleDirections.size() - 1; i >= 0; --i) {
			Point currentPoint = possibleDirections.get(i);

			int distXToNest = ant.getNest().getX() - ant.getX();
			int distYToNest = ant.getNest().getY() - ant.getY();
			if (distXToNest != 0 || distYToNest != 0) {
				Point lastDirection = Direction.getDirection(Direction.getDirection(distXToNest, distYToNest));
				int currentSimiliarities = 0;
				if ((currentPoint.x == -lastDirection.x && fromNestAway) || (currentPoint.x == lastDirection.x && !fromNestAway)) {
					currentSimiliarities++;
				}
				if ((currentPoint.y == -lastDirection.y && fromNestAway) || (currentPoint.y == lastDirection.y && !fromNestAway)) {
					currentSimiliarities++;
				}
				if (currentSimiliarities > directionSimilarities) {
					directionSimilarities = currentSimiliarities;
				}
			}
		}

		int distXToNest = ant.getNest().getX() - ant.getX();
		int distYToNest = ant.getNest().getY() - ant.getY();
		if (distXToNest != 0 || distYToNest != 0) {
			Point lastDirection = Direction.getDirection(Direction.getDirection(distXToNest, distYToNest));

			for (int i = possibleDirections.size() - 1; i >= 0; --i) {
				Point currentPoint = possibleDirections.get(i);

				int currentSimiliarities = 0;
				if ((currentPoint.x == -lastDirection.x && fromNestAway) || (currentPoint.x == lastDirection.x && !fromNestAway)) {
					currentSimiliarities++;
				}
				if ((currentPoint.y == -lastDirection.y && fromNestAway) || (currentPoint.y == lastDirection.y && !fromNestAway)) {
					currentSimiliarities++;
				}
				int currentWeight = weightMap.get(currentPoint);
				weightMap.remove(currentPoint);
				currentWeight += currentSimiliarities * currentSimiliarities;
				weightMap.put(currentPoint, currentWeight);
			}
		}
		return weightMap;
	}

	/** Gewichtung: Bewegung möglichst gerade und nicht quer halten */
	protected HashMap<Point, Integer> doEasyMove(final ArrayList<Point> l, final Ant ant, HashMap<Point, Integer> weightMap, final ActionMessageGroup g) {
		ArrayList<Point> possibleDirections = new ArrayList<Point>(l);
		int movePoints = 0;
		for (Point p : possibleDirections) {
			int currentMovePoints = 0;
			if (p.x != 0) {
				currentMovePoints++;
			}
			if (p.y != 0) {
				currentMovePoints++;
			}
			if (movePoints == 0 || currentMovePoints < movePoints) {
				movePoints = currentMovePoints;
				if (currentMovePoints == 1) {
					break;
				}
			}
		}

		for (int i = possibleDirections.size() - 1; i >= 0; --i) {
			Point p = possibleDirections.get(i);

			int currentMovePoints = 0;
			if (p.x != 0) {
				currentMovePoints++;
			}
			if (p.y != 0) {
				currentMovePoints++;
			}

			int value = weightMap.get(p);
			weightMap.remove(p);
			value += currentMovePoints * currentMovePoints;
			weightMap.put(p, value);
		}
		g.addMessage(new ActionMessage("Gewichtung für einfache Bewegung"));

		return weightMap;
	}

	/** Pheromone beachten */
	protected ArrayList<Point> lookForPheromons(final ArrayList<Point> l, final Ant ant, final ActionMessageGroup g, boolean lookForMax) {
		ArrayList<Point> possibleDirections = new ArrayList<Point>(l);

		int minPheromons = -1;
		int maxPheromons = -1;
		for (Point p : possibleDirections) {
			int pheromons = World.getInstance().getAmountOfPheromons(ant.getX() + p.x, ant.getY() + p.y);
			if (minPheromons == -1 || pheromons < minPheromons) {
				minPheromons = pheromons;
			}
			if (maxPheromons == -1 || pheromons > maxPheromons) {
				maxPheromons = pheromons;
			}
		}

		for (int i = possibleDirections.size() - 1; i >= 0; --i) {
			Point currentPoint = possibleDirections.get(i);

			if (!lookForMax) {
				if (World.getInstance().getAmountOfPheromons(ant.getX() + currentPoint.x, ant.getY() + currentPoint.y) > minPheromons) {
					g.addMessage(new ActionMessage("Habe Punkt (" + (ant.getX() + possibleDirections.get(i).x) + "|"
							+ (ant.getY() + possibleDirections.get(i).y) + ") entfernt weil dort zu wenig Pheromone lagen", possibleDirections.get(i)));
					possibleDirections.remove(i);
				}
			}
			else {
				if (World.getInstance().getAmountOfPheromons(ant.getX() + currentPoint.x, ant.getY() + currentPoint.y) < minPheromons) {
					g.addMessage(new ActionMessage("Habe Punkt (" + (ant.getX() + possibleDirections.get(i).x) + "|"
							+ (ant.getY() + possibleDirections.get(i).y) + ") entfernt weil dort zu viele Pheromone lagen", possibleDirections.get(i)));
					possibleDirections.remove(i);
				}
			}
		}
		return possibleDirections;
	}
	
	private int calculatePheromons(Ant ant, Point p) {
		int pheromonsOnPoint = World.getInstance().getAmountOfPheromons(ant.getX() + p.x, ant.getY() + p.y);
		int pheromonsAround = getPheromonsAroundPoint(ant.getX()+p.x,ant.getY()+p.y);
		return pheromonsOnPoint + (pheromonsAround / 4);
	}
	
	private int getPheromonsAroundPoint(int newX, int newY) {
		int pheromons = 0;
		
		for (int x=-1;x<=1;x+=2) {
			for (int y=-1;y<=1;y+=2) {
				int xC = newX + x;
				int yC = newY + y;
				
				if ((x != 0 || y != 0) && x >= 0 && y >= 0 && x < World.getInstance().getMap().getWidth() && y < World.getInstance().getMap().getHeight()) {
					pheromons += World.getInstance().getAmountOfPheromons(xC, yC);
				}
			}
		}
		
		return pheromons;
	}

	/**
	 * Gewichtet die verschiedenen Bewegungen nach Anzahl der dort liegenden Pheromone Je mehr Pheromone liegen, desto besser die Gewichtung
	 */
	protected HashMap<Point, Integer> evaluateMostPheromons(final ArrayList<Point> l, final Ant ant, final ActionMessageGroup g) {
		HashMap<Point, Integer> map = new HashMap<Point, Integer>();
		for (Point p : l) {
			int amountOfPheromons = calculatePheromons(ant,p);
			map.put(p, amountOfPheromons);
		}
		g.addMessage(new ActionMessage("Bewegungen wurden nach Anzahl der meisten Pheromone gewichtet."));
		return map;
	}

	/**
	 * Gewichtet die verschiedenen Bewegungen nach Anzahl der dort liegenden Pheromone Je weniger Pheromone liegen, desto besser die Gewichtung
	 */
	protected HashMap<Point, Integer> evaluateFewestPheromons(final ArrayList<Point> l, final Ant ant, final ActionMessageGroup g) {
		HashMap<Point, Integer> map = new HashMap<Point, Integer>();

		int maxPheromons = -1;
		for (Point p : l) {
			int currentPheromons = World.getInstance().getAmountOfPheromons(ant.getX() + p.x, ant.getY() + p.y) + getPheromonsAroundPoint(ant.getX()+p.x,ant.getY()+p.y);
			if (maxPheromons == -1 || currentPheromons > maxPheromons) {
				maxPheromons = currentPheromons;
			}
		}

		for (Point p : l) {
			int weight = maxPheromons - (World.getInstance().getAmountOfPheromons(ant.getX() + p.x, ant.getY() + p.y) + getPheromonsAroundPoint(ant.getX()+p.x,ant.getY()+p.y));
			map.put(p, weight);
			g.addMessage(new ActionMessage("Gewicht (" + (ant.getX() + p.x) + "|" + (ant.getY() + p.y) + "): " + weight));
		}
		g.addMessage(new ActionMessage("Bewegungen wurden nach Anzahl der wenigsten Pheromone gewichtet."));
		return map;
	}

	/**
	 * Versucht Zufälle zu minimieren durch Potenzierung
	 */
	protected HashMap<Point, Integer> lessLuckCheck(final ArrayList<Point> l, final Ant ant, HashMap<Point, Integer> weightMap, final ActionMessageGroup g) {
		for (Point p : l) {
			int value = weightMap.get(p);
			weightMap.remove(p);
			weightMap.put(p, value * value);
		}
		g.addMessage(new ActionMessage("Zufallsminderung."));
		return weightMap;
	}

	/**
	 * Gibt die restlichen Richtungen aus
	 */
	protected void echoDirections(final ArrayList<Point> l, final Ant ant, final ActionMessageGroup g) {
		StringBuffer mes = new StringBuffer("Es existieren noch Bewegungen nach: ");

		boolean first = true;
		for (Point p : l) {
			if (!first) {
				mes.append(", ");
			}
			mes.append("(" + (ant.getX() + p.x) + "|" + (ant.getY() + p.y) + ")");
			first = false;
		}
		g.addMessage(new ActionMessage(mes.toString()));
	}

	/**
	 * Gibt die restlichen Richtungen aus
	 */
	protected void echoDirections(final ArrayList<Point> l, final Ant ant, HashMap<Point, Integer> weightMap, final ActionMessageGroup g) {
		StringBuffer mes = new StringBuffer("echo: ");

		boolean first = true;
		for (Point p : l) {
			if (!first) {
				mes.append(",\n");
			}
			mes.append("(" + (ant.getX() + p.x) + "|" + (ant.getY() + p.y) + ")" + " -> " + weightMap.get(p).intValue());
			first = false;
		}
		g.addMessage(new ActionMessage(mes.toString()));
	}

	/** Bewegung durchführen */
	protected void doMove(final Point position, final Ant ant, final ActionMessageGroup g) {
		ant.walkTo(position.x, position.y);
		g.addMessage(new ActionMessage("Habe mich bewegt: (" + (ant.getX()) + "|" + (ant.getY()) + ")", position));
		ant.addMessage(g);
	}

	/** Bewegung durchführen */
	protected void doMove(final ArrayList<Point> possibleDirections, final Ant ant, final ActionMessageGroup g) {
		if (possibleDirections.size() > 0) {
			int rand = (int) (Math.random() * (possibleDirections.size()));

			Point direction = possibleDirections.get(rand);
			ant.walkTo(ant.getX() + direction.x, ant.getY() + direction.y);
			g.addMessage(new ActionMessage("Habe mich bewegt: (" + (ant.getX()) + "|" + (ant.getY()) + ")", direction));
			ant.addMessage(g);
		}
	}

	/** Bewegung durchführen */
	protected void doMove(final ArrayList<Point> possibleDirections, final Ant ant, final HashMap<Point, Integer> weightMap, final ActionMessageGroup g) {
		if (possibleDirections.size() > 0) {
			int weights = 0;
			for (Point p : possibleDirections) {
				weights += weightMap.get(p);
			}
			Point[] chanceField = new Point[weights + possibleDirections.size()];
			int currentIndex = 0;
			for (Point p : possibleDirections) {
				chanceField[currentIndex++] = p;
				for (int i = 0; i < weightMap.get(p); ++i) {
					chanceField[currentIndex++] = p;
				}
			}
			int rand = (int) (Math.random() * (chanceField.length));

			Point direction = chanceField[rand];
			ant.walkTo(ant.getX() + direction.x, ant.getY() + direction.y);
			g.addMessage(new ActionMessage("Habe mich bewegt: (" + (ant.getX()) + "|" + (ant.getY()) + ")", direction));
			ant.addMessage(g);
		}
	}

	public abstract String getName();
}